﻿using Microsoft.Phone.Info;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Navigation;
using System.Windows.Threading;
using TakePhotoTogether.Communication;
using TakePhotoTogether.Communication.Protocols;
using TakePhotoTogether.ImageProcessing;
using TakePhotoTogether.Resources;
using Windows.Networking.Sockets;
using Windows.Phone.Media.Capture;

namespace TakePhotoTogether.Controller
{
    public class MainController : IRemoteDataListener
    {
        #region NFC Action events
        public Action OnPeerCanceled = null;
        public Action OnPeerFailed = null;
        public Action OnPeerFinderCompleted = null;
        public Action OnPeerStarted = null;
        public Action OnPeerFound = null;
        public Action OnSocketConnectionDispose = null;
        public Action OnRemoteUserFinishedConnection = null;
        #endregion

        #region Stream events
        /// <summary> Called when receive stream data </summary>
        public Action<MemoryStream> OnStreamDataReceived = null;
        /// <summary> Called when stream error occurs </summary>
        public Action OnStreamDataError = null;
        #endregion

        #region Variables related to QoE
        private int numberFrames = 0;
        private Stopwatch connectionStopWatch;
        private Stopwatch timeBetweenFrames;
        private long lastFrameSpan = 0;
        private const int connectionTimeOutInterval = 10000;
        /// <summary> Indicates that remote device is on background </summary>
        private bool backgroundStreamMode = false;
        #endregion

        #region Singleton Pattern
        private static MainController _instance = null;
        public static MainController Singleton
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new MainController();
                }

                return _instance;
            }
        }
        #endregion

        #region Public Properties
        private bool _isNullStreamingMode = false;
        public bool IsNullStreamingMode
        {
            get { return _isNullStreamingMode; }
            set
            {
                _isNullStreamingMode = value;
                streamManager.IsNullStreamingMode = value;
            }
        }

        private bool _orientationChange = false;
        public bool OrientationChange
        {
            get { return _orientationChange; }
            set 
            {
                _orientationChange = value;
                streamManager.OrientationChange = value;
            }
        }

        public bool IsBluetoothEnabled
        {
            get { return NfcManager.Singleton.IsBluetoothEnabled; }
            set { NfcManager.Singleton.IsBluetoothEnabled = value; }
        }

        private bool _isConnected = false;
        public bool IsConnected
        {
            get { return _isConnected; }
        }

        private bool _isPeerFound = false;
        public bool IsPeerFound
        {
            get { return _isPeerFound || _isConnected; }
        }

        private bool _noTap = false;
        public bool NoTap
        {
            get
            {
                return _noTap;
            }
            set
            {
                _noTap = value;

                if (_isConnected)
                {
                    DataPacketFactory.GetLowBatteryPacket().Send();
                }
            }
        }

        /// <summary> Represents the last image frame received from remote partner </summary>
        public StreamImage LastPartnerImage
        {
            get
            {
                return streamManager.LastPartnerImage;
            }
        }
        #endregion

        #region Logic Control Variables
        /// <summary> Variables that stores current screen </summary>
        private string currentScreen = "";

        /// <summary> Action to change current screen to MainPage</summary>
        private Action goToMainPage = null;
        
        /// <summary> Variable to indicate that app must be terminated when connection is disposed by remote user</summary>
        private bool isGoingToTerminate = false;

        /// <summary> Variable to handle multiple attempts to show "Connection Lost" message</summary>
        private bool alreadyShowedConnectionLost = false;

        /// <summary> Semaphore to avoid multiple calls of BackKey method at same time </summary>
        private bool backKeySemaphore = false;
        #endregion

        #region controllers
        /// <summary> Instance of Manager of Streaming activities </summary>
        private StreamManager streamManager = null;
        #endregion

        #region Debug Variables
        /// <summary> Debug variable to turn off services </summary>
        private bool servicesActive = false;
        #endregion

        /// <summary>
        /// The unique constructor of MainController. Must be private to enforce Singleton usage.
        /// </summary>
        private MainController()
        {
            /** Setup NFC events **/
            NfcManager.Singleton.OnPeerFound = OnNfcPeerFound;
            NfcManager.Singleton.OnPeerFinderStarted = OnNfcPeerStarted;
            NfcManager.Singleton.OnPeerFinderCompleted = OnNfcPeerFinderCompleted;
            NfcManager.Singleton.OnPeerFailed = OnNfcPeerFailed;
            NfcManager.Singleton.OnPeerCanceled = OnNfcPeerCanceled;

            /** Setup Socket events **/
            SocketManager.GetSocketManager().OnSocketDispose += onSocketDispose;

            // Start NFC Observer
            NfcManager.Singleton.StartPeerFinder();

            // Start controller instances
            RTTController.GetInstance();
            SyncManager.GetSyncManager();
            streamManager = new StreamManager();

            /** Setup Stream events **/
            streamManager.OnFrameReceived = OnFrameReceived;
            streamManager.OnStreamError = OnStreamError;

            // Stopwatches to control time related to frame receiving
            connectionStopWatch = new Stopwatch();
            timeBetweenFrames = new Stopwatch();

            // Setup of listener of packet receiving
            SocketManager.GetSocketManager().AddDataListener(this, (int)MessageTypes.BackgroundStreamPacket);
            SocketManager.GetSocketManager().AddDataListener(this, (int)MessageTypes.CloseAppPacket);
            SocketManager.GetSocketManager().AddDataListener(this, (int)MessageTypes.LowBatteryPacket);
        }

        /// <summary>
        /// Set current view. 
        /// Allows specific logic regarding current view when some NFC/Socket events are launched.
        /// </summary>
        /// <param name="screen">current view name (must be unique)</param>
        /// <param name="goToMainPage"> Action that change view to MainPage </param>
        public void SetCurrentScreen(string screen, Action goToMainPage)
        {
            currentScreen = screen;

            this.goToMainPage = goToMainPage;
        }

        /// <summary>
        /// Setup PhotoCaptureDevice used for stream management
        /// </summary>
        /// <param name="camera"></param>
        public void SetupCamera(PhotoCaptureDevice camera)
        {
            if (camera != null)
            {
                streamManager.SetupCamera(camera);
                IsCameraInitialized = true;

                if (_isConnected && numberFrames >= 1)
                {
                    StartServices();
                }
            }
            else
            {
                StopServices();
                IsCameraInitialized = false;
            }
        }

        /// <summary>
        /// Handle error on Stream sending or receiving
        /// </summary>
        private void OnStreamError()
        {
            if (OnStreamDataError != null)
            {
                OnStreamDataError();
            }
        }

        /// <summary>
        /// Redirects received frame to update My Friend's View
        /// </summary>
        /// <param name="obj"></param>
        private async void OnFrameReceived(System.IO.MemoryStream obj)
        {
            Debug.WriteLine("MainController::OnFrameReceived");

            // Counting number of received frames
            numberFrames++;

            // Get time between frames receiving
            lastFrameSpan = timeBetweenFrames.ElapsedMilliseconds;
            timeBetweenFrames.Restart();

            // Only updates My Friend's View if current screen is MainPage
            if ("MainPage".Equals(currentScreen) && OnStreamDataReceived != null && obj != null)
            {
                OnStreamDataReceived(obj);
            }

            if (numberFrames == 1)
            {
                await Task.Delay(200);
                if (IsCameraInitialized)
                {
                    StartServices();
                }

                Thread connectedVerification = new Thread(() =>
                {
                    int count = 0;

                    while (_isConnected)
                    {
                        if (IsCameraInitialized)
                        {
                            StartServices();
                        }

                        Thread.Sleep(1000);
                    }
                });
                connectedVerification.IsBackground = true;
                connectedVerification.Start();
            }
        }

        /// <summary>
        /// Method to change current view to main page on first frame received
        /// </summary>
        private void CheckAndGoToMainPage()
        {
            // Go To MainPage
            if (numberFrames == 0 && currentScreen != "MainPage" && goToMainPage != null)
            {
                goToMainPage();
            }
        }

        /// <summary>
        /// Called everytime PeerFinder detects user canceled connection
        /// </summary>
        private void OnNfcPeerCanceled()
        {
            Debug.WriteLine("MainController::OnNfcPeerCanceled");

            _isPeerFound = false;
            _isConnected = false;
            numberFrames = 0;

            if (OnPeerCanceled != null)
            {
                OnPeerCanceled();
            }
        }

        /// <summary>
        /// Called everytime PeerFinder receives a Failure on connection attempt
        /// </summary>
        private void OnNfcPeerFailed()
        {
            Debug.WriteLine("MainController::OnNfcPeerFailed");

            ForcePeerFail();
        }

        /// <summary>
        /// Called every time PeerFinder complete a connection
        /// </summary>
        /// <param name="socket"></param>
        private void OnNfcPeerFinderCompleted(StreamSocket socket)
        {
            Debug.WriteLine("MainController::OnNfcPeerCompleted");

            //if (_isConnected)
            //{
            //    DataPacketFactory.GetCloseAppPacket().Send();
            //}

            // Handle error when socket is null
            if (socket == null)
            {
                return;
            }

            // Update variables regarding connection
            _isPeerFound = false;
            _isConnected = true;

            // Go To MainPage
            CheckAndGoToMainPage();

            // Loop running in a thread to assure that current screen is MainPage
            // before socket setup
            Thread setupThread = new Thread(() =>
                {
                    Thread.Sleep(100);

                    // Waiting current screen is MainPage
                    while(_isConnected && !"MainPage".Equals(currentScreen))
                    {
                        Thread.Sleep(1000);
                    }

                    // If connection was not disposed keep going to Socket Setup
                    if (_isConnected)
                    {
                        SetupSocket(socket);
                    }
                });
            setupThread.IsBackground = true;
            setupThread.Start();
        }

        private void OnNfcPeerStarted()
        {
            Debug.WriteLine("MainController::OnNfcPeerStarted");

            if (OnPeerStarted != null)
            {
                OnPeerStarted();
            }
        }

        private void OnNfcPeerFound()
        {
            Debug.WriteLine("MainController::OnNfcPeerFound");
            _isPeerFound = true;

            // Go To MainPage
            CheckAndGoToMainPage();

            Thread timeOut = new Thread(() =>
            {
                int timeCount = 0;

                while (timeCount < connectionTimeOutInterval)
                {
                    if (_isConnected && numberFrames >= 2)
                    {
                        return;
                    }
                    Thread.Sleep(200);
                    timeCount += 200;
                }

                if (!_isConnected || numberFrames < 2)
                {
                    ForcePeerFail();
                }
            });
            timeOut.IsBackground = true;
            timeOut.Start();

            if (OnPeerFound != null)
            {
                OnPeerFound();
            }
        }

        public void OnNavigatedTo()
        {
            Debug.WriteLine("MainController::OnNavigatedTo");

            if (_isConnected)
            {
                IsNullStreamingMode = false;
                //backgroundStreamMode = false;
                timeBetweenFrames.Reset();
                DataPacketFactory.GetBackgroundStreamPacket(false).Send();
            }
        }

        public void OnNavigatedFrom()
        {
            Debug.WriteLine("MainController::OnNavigatedFrom");

            if (_isConnected)
            {
                timeBetweenFrames.Reset();
                IsNullStreamingMode = true;

                //SocketManager.GetSocketManager().ClearQueue();
                DataPacketFactory.GetBackgroundStreamPacket(true).Send();
            }
        }

        #region Application General Events - Public Methods

        // Code to execute when the application is deactivated (sent to background)
        // This code will not execute when the application is closing
        public void OnApplicationDeactivated()
        {
            
        }

        public void OnApplicatonBackground()
        {
            
        }

        public void OnApplicationActivated()
        {
            
        }

        // Code to execute when the application is closing (eg, user hit Back)
        // This code will not execute when the application is deactivated
        public void OnApplicationClosing()
        {
            NfcManager.Singleton.StopPeerFinder();
        }

        #endregion

        public void StartServices()
        {
            servicesActive = true;
            Debug.WriteLine("MainController::StartServices");

            RTTController.GetInstance().StartTimeMeasure();
            streamManager.StartSendingStream();
        }

        public void StopServices()
        {
            servicesActive = false;
            Debug.WriteLine("MainController::StopServices");

            RTTController.GetInstance().StopTimeMeasure();
            streamManager.StopSendingStream();
        }

        /// <summary>
        /// Setup socket on SocketManager and start services
        /// </summary>
        /// <param name="socket"></param>
        private async void SetupSocket(StreamSocket socket)
        {
            // Variable to avoid multiple exhibition of 
            alreadyShowedConnectionLost = false;

            // Reset number of frames to zero
            // Starting counting only after Socket Setup
            numberFrames = 0;

            // Start stopwatches related to frame receiving
            connectionStopWatch.Start();
            timeBetweenFrames.Start();

            // Setup socket on SocketManager
            SocketManager.GetSocketManager().SetupChannel(new StreamSocketConnection(socket));

            // Send User Identification to Remote Peer
            string userId = (string)UserExtendedProperties.GetValue("ANID2");
            DataPacketFactory.GetIdentificationPacket(userId == null ? "userId" : userId).Send();

            if (OnPeerFinderCompleted != null)
            {
                OnPeerFinderCompleted();
            }

            Thread startStreamThread = new Thread(() =>
                {
                    while(!IsCameraInitialized)
                    {
                        Thread.Sleep(200);
                    }

                    streamManager.SendFirstStreaming();
                });
            startStreamThread.IsBackground = true;
            startStreamThread.Start();
        }

        private void ForcePeerFail()
        {
            Debug.WriteLine("MainController::ForcePeerFail");

            if (OnPeerFailed != null)
            {
                OnPeerFailed();
            }
            _isPeerFound = false;
            StopServices();

            SocketManager.GetSocketManager().Dispose();
        }

        private void onSocketDispose()
        {
            Debug.WriteLine("MainController::OnSocketDispose");

            if (isGoingToTerminate)
            {
                Application.Current.Terminate();
                return;
            }

            _isPeerFound = false;
            _isConnected = false;
            numberFrames = 0;
            backgroundStreamMode = false;

            connectionStopWatch.Reset();
            timeBetweenFrames.Reset();

            StopServices();

            // Delete all placeholders when connection is finished
            Thread deletePlaceholdersThread = new Thread(() =>
            {
                Thread.Sleep(3000);
                PlaceHolderManager.DeleteAllPlaceHolders();
            });
            deletePlaceholdersThread.IsBackground = true;
            deletePlaceholdersThread.Start();

            if (OnSocketConnectionDispose != null)
            {
                OnSocketConnectionDispose();
            }
        }

        public void FinishConnection()
        {
            StopServices();
            SocketManager.GetSocketManager().ClearQueue();

            DataPacketFactory.GetCloseAppPacket().Send();
        }

        public void ReceiveData(PayloadData data)
        {
            if (data is BackgroundStreamData)
            {
                BackgroundStreamData streamData = data as BackgroundStreamData;

                Debug.WriteLine("MainController::ReceiveData => BackgroundStream::active - " + streamData.Active);

                //backgroundStreamMode = streamData.Active;

                //if (backgroundStreamMode)
                //{
                //    //StopServices();
                //}
                //else
                //{
                //    if (_isConnected)
                //    {
                //        StartServices();
                //    }
                //}
            }
            else if (data is CloseAppData && _isConnected)
            {
                Debug.WriteLine("MainController::ReceiveData => Friend closes the app");

                Debug.WriteLine(AppResources.Error_Exit_Friend_Message);

                _isConnected = false;

                StopServices();

                if (!alreadyShowedConnectionLost && OnRemoteUserFinishedConnection != null)
                {
                    alreadyShowedConnectionLost = true;

                    OnRemoteUserFinishedConnection();
                }

                SocketManager.GetSocketManager().Dispose();
            }
            else if (data is LowBatteryData && _isConnected)
            {
                ForcePeerFail();
            }
        }

        public void OnBackKeyPressed(Dispatcher dispatcher)
        {
            if (backKeySemaphore)
            {
                return;
            }

            backKeySemaphore = true;

            if (!_isConnected)
            {
                return;
            }

            Debug.WriteLine("MainController::OnBackKeyPressed");

            StopServices();

            SocketManager.GetSocketManager().ClearQueue();
            timeBetweenFrames.Reset();
            DataPacketFactory.GetBackgroundStreamPacket(true).Send();

            dispatcher.BeginInvoke(() =>
                {
                    if (MessageBox.Show(AppResources.Error_Exit_Message, AppResources.Error_Exit_Message_Title, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
                    {
                        DataPacketFactory.GetCloseAppPacket().Send();
                        Debug.WriteLine("Close App");

                        isGoingToTerminate = true;

                        Application.Current.Terminate();
                        
                    }
                    else
                    {
                        DataPacketFactory.GetBackgroundStreamPacket(false).Send();
                        //backgroundStreamMode = false;
                        StartServices();
                    }
                });

            backKeySemaphore = false;
        }

        public void OnMainViewStateChanged(ViewState state)
        {
            DataPacket packet = new DataPacket()
            {
                MessageType = (int)MessageTypes.ViewStatePacket,
                DataSize = 1,
                Payload = new ViewStateData(state)
            };

            packet.Send();
        }

        public bool IsCameraInitialized { get; set; }
    }
}
